//Copyright 2010 Balazs Keri, Imre Keri
//
//This file is part of FireworksCreator.
//
//FireworksCreator is free software: you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//(at your option) any later version.
//
//FireworksCreator is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.
//
//You should have received a copy of the GNU General Public License
//along with FireworksCreator.  If not, see <http://www.gnu.org/licenses/>.
//
#include "particlefactory.h"
#include <vector>
#include "model/emittertemplate.h"
#include "particle.h"
#include "controller.h"
#include "emitterevaluator.h"
//
namespace player
{
using namespace model;
ParticleFactory::TimeMark::TimeMark(const RandomDouble &p_time,ParticleTemplate::TimeMark::Mode p_mode,int p_other)
:
   time(p_time),mode(p_mode),other(p_other)
{
}
//
ParticleFactory::EmitterBlock::EmitterBlock(int p_t_start,Emitter *p_emitter)
:
   t_start(p_t_start),t_end(p_t_start),
   dt_min(0),dt_max(0),
   emitter(p_emitter)
{
}
ParticleFactory::EmitterBlock::EmitterBlock(int p_t_start,int p_t_end,double p_dt_min,double p_dt_max,Emitter *p_emitter)
:
   t_start(p_t_start),t_end(p_t_end),
   dt_min(p_dt_min),dt_max(p_dt_max),
   emitter(p_emitter)
{
}
//
ParticleFactory::ParticleFactory()
:
   controller(0),
   particle_template(0),
   particle_system(0)
{
}
ParticleFactory::~ParticleFactory()
{
   qDebug()<<"Player::ParticleFactory::~ParticleFactory";
}
void ParticleFactory::init_setup(Controller *p_controller,ParticleSystem *p_particle_system,const ParticleTemplate *p_particle_template)
{
   Q_ASSERT(!controller&&!particle_template&&!particle_system);
   //
   controller=p_controller;
   particle_template=p_particle_template;
   particle_system=p_particle_system;
   setup_time_marks();
}
//
Particle *ParticleFactory::create()
{
   return new Particle(particle_system,particle_template->color_control_points.count(),particle_template->alpha_control_points.count(),particle_template->size_control_points.count(),particle_template->radius_control_points.count(),particle_template->emitters.count());
}
void ParticleFactory::setup(Particle *p_particle,const ParticleParam &p_param)
{
   double lifetime=particle_template->get_life_time().data().calculate();
   //
   std::vector<double> time_mark_values;
   time_mark_values.reserve(time_marks.count()+2);
   //two "special time mark values" for start and end time
   time_mark_values.push_back(0);
   time_mark_values.push_back(lifetime);
   for(int i=0;i<time_marks.count();++i)
   {
      double t=time_marks[i].time.data().calculate();
      switch(time_marks[i].mode)
      {
      case ParticleTemplate::TimeMark::from_start:
         //nothing to do
         break;
      case ParticleTemplate::TimeMark::from_end:
         t=lifetime-t;
         break;
      case ParticleTemplate::TimeMark::from_other:
         Q_ASSERT(time_marks[i].other<i);
         t=time_mark_values[2+time_marks[i].other]+t;//normal values for time marks start at index 2 in 'time_mark_values'
         break;
      }
      time_mark_values.push_back(t);
   }
   //
   //no special handling of first and last control point needed here
   for(int i=0;i<color_control_point_index.count();++i)
   {
      p_particle->color_evaluator.set_control_point(i,particle_template->color_control_points[i].color.data().calculate(),time_mark_values[color_control_point_index[i]]);
   }
   for(int i=0;i<alpha_control_point_index.count();++i)
   {
      p_particle->alpha_evaluator.set_control_point(i,particle_template->alpha_control_points[i].value.data().calculate(),time_mark_values[alpha_control_point_index[i]]);
   }
   for(int i=0;i<size_control_point_index.count();++i)
   {
      p_particle->size_evaluator.set_control_point(i,particle_template->size_control_points[i].value.data().calculate(),time_mark_values[size_control_point_index[i]]);
   }
   for(int i=0;i<mass_control_point_index.count();++i)
   {
      p_particle->radius_evaluator.set_control_point(i,particle_template->radius_control_points[i].value.data().calculate(),time_mark_values[mass_control_point_index[i]]);
   }
   //
   p_particle->set_position(p_param.position);
   p_particle->set_angle(p_param.angle);
   p_particle->set_velocity(p_param.velocity);
   p_particle->set_angular_velocity(p_param.angular_velocity);
   p_particle->set_life_time(lifetime);
   //
   for(unsigned int i=0;i<static_cast<unsigned int>(emitter_blocks.count());++i)
   {
      const EmitterBlock &eb=emitter_blocks[i];
      if(eb.t_start==eb.t_end)
         p_particle->set_emitter_evaluator(i,EmitterEvaluator(eb.emitter,time_mark_values[eb.t_start]));
      else
         p_particle->set_emitter_evaluator(i,EmitterEvaluator(eb.emitter,time_mark_values[eb.t_start],time_mark_values[eb.t_end],eb.dt_min,eb.dt_max));
   }
}
//
template<typename CP> QList<int> fill_control_point_index(const QList<CP> &cp,const QHash<QString,int> &name_to_index)
{
   QList<int> ret;//the return value
   QListIterator<CP> cpi(cp);
   //at first and last control points 'time' is not used and must be empty
   ret.append(0);//first point always at time 0 which is a "special time mark value" at index 0 (see at 'setup')
   while(cpi.hasNext())
   {
      const CP &cp=cpi.next();
      if(!cp.time.isEmpty())
         ret.append(2+name_to_index[cp.time]);//add 2 to be after the "special time mark values"
   }
   if(cp.count()>1)
      ret.append(1);//last point always at lifetime which is a "special time mark value" at index 1 (see at 'setup')
   return ret;
}
//
void ParticleFactory::setup_time_marks()
{
   //name_to_index will contain a mapping between time mark name and index in this->time_marks
   QHash<QString,int> name_to_index;
   //
   //fill this->time_marks with items in order that if mode is from_other the other item is preceding the current one
   QHashIterator<QString,ParticleTemplate::TimeMark> tm(particle_template->time_marks);
   QStack<QString> s;
   while(tm.hasNext()||!s.empty())
   {
      if(s.empty())
      {
         tm.next();
         s.push(tm.key());
      }
      const ParticleTemplate::TimeMark &m=particle_template->time_marks.value(s.top());
      if(m.mode==ParticleTemplate::TimeMark::from_other&&!name_to_index.contains(m.other))
      {
         Q_ASSERT(!s.contains(m.other));
         s.push(m.other);
      }
      else
      {
         time_marks.append(TimeMark(m.time,m.mode,(m.mode==ParticleTemplate::TimeMark::from_other)?name_to_index[m.other]:0));
         name_to_index.insert(s.top(),time_marks.count()-1);
         s.pop();
      }
   }
   //
   color_control_point_index=fill_control_point_index<ParticleTemplate::ColorControlPoint>(particle_template->color_control_points,name_to_index);
   alpha_control_point_index=fill_control_point_index<ParticleTemplate::FloatControlPoint>(particle_template->alpha_control_points,name_to_index);
   size_control_point_index=fill_control_point_index<ParticleTemplate::FloatControlPoint>(particle_template->size_control_points,name_to_index);
   mass_control_point_index=fill_control_point_index<ParticleTemplate::FloatControlPoint>(particle_template->radius_control_points,name_to_index);
   //
   QListIterator<ParticleTemplate::EmitterBlock> ebi(particle_template->emitters);
   while(ebi.hasNext())
   {
      const ParticleTemplate::EmitterBlock &eb=ebi.next();
      Emitter* e=controller->emitter_for_template(eb.emitter);
      if(eb.time2.isEmpty())
      {
         Q_ASSERT(name_to_index.contains(eb.time1));
         emitter_blocks.append(EmitterBlock(2+name_to_index[eb.time1],e));
      }
      else
      {
         Q_ASSERT(name_to_index.contains(eb.time1) && name_to_index.contains(eb.time2));
         emitter_blocks.append(EmitterBlock(2+name_to_index[eb.time1],2+name_to_index[eb.time2],eb.interval_min,eb.interval_max,e));
      }
   }
}
}
